rvWebServer = function(int_port, requestCallback) {

	this.CONTENT_TYPE_JAVASCRIPT = "text/javascript; charset=UTF-8";
	this.CONTENT_TYPE_TEXT_XML = "text/xml; charset=UTF-8";
	this.CONTENT_TYPE_TEXT_PLAIN = "text/plain";
	this.CONTENT_TYPE_TEXT_HTML = "text/html";
	this.CONTENT_TYPE_VIDEO_MP4 = "video/mp4";

	this.HTTP_OK_TEXT = "200 OK";
// this.HTTP_MOVED_TEMP_TEXT = "302 Found. Rise Cache.";
	this.HTTP_BAD_REQUEST_TEXT = "400 Bad Request. Rise Cache.";
	this.HTTP_NOT_FOUND_TEXT = "404 Not Found. Rise Cache.";
//  this.HTTP_CONNECTION_REFUSED_TEXT = "404 Connection refused.Rise Cache.";
//  this.HTTP_CLIENT_TIMEOUT_TEXT = "408 Request Timeout. Rise Cache.";
//  this.HTTP_INSUFFICIENT_SPACE_TEXT = "500 Internal Server Error. Insufficient Space. Rise Cache.";
//  this.HTTP_INTERNAL_ERROR_TEXT = "500 Internal Server Error. Rise Cache.";

	
	var socket = chrome.socket;
	var socketInfo;
	var port = int_port || 9999;
	var requestCallback = requestCallback;
	this.openSockets = new rvHashTable(); // key = socket id


	var stringToUint8Array = function(string, returnBuffer) {
		var buffer = new ArrayBuffer(string.length);
		var view = new Uint8Array(buffer);
		for ( var i = 0; i < string.length; i++) {
			view[i] = string.charCodeAt(i);
		}
		if (returnBuffer)
			return buffer;
		else
			return view;
	};

	var arrayBufferToString = function(buffer) {
		var str = '';
		var uArrayVal = new Uint8Array(buffer);
		for ( var s = 0; s < uArrayVal.length; s++) {
			str += String.fromCharCode(uArrayVal[s]);
		}
		return str;
	};

	var logToScreen = function(log) {
		logger.textContent += log + "\n";
	};

	var onAccept = function(acceptInfo) {
		console.log("ACCEPT", acceptInfo);
		// continue accepting connections
		socket.accept(socketInfo.socketId, onAccept);
		if (acceptInfo.resultCode === 0) {
			readFromSocket(acceptInfo.socketId);
		}
	};
	
	var readFromSocket = function(socketId) {
		// Read in the data
		socket.read(socketId, function(readInfo) {
			console.log("READ", readInfo);
            if (readInfo.resultCode < 0) {
                console.error("socket is dead");
                socket.destroy(socketId);
                return;
            }
			// Parse the request.
			var data = arrayBufferToString(readInfo.data);
			if (data.indexOf("GET ") == 0) {
				var keepAlive = false;
				if (data.indexOf("Connection: keep-alive") != -1) {
					keepAlive = true;
				}

				// we can only deal with GET requests
				var uriEnd = data.indexOf(" ", 4);
				if (uriEnd < 0) { /* throw a wobbler */
					return;
				}
				var uri = data.substring(4, uriEnd);
				// strip query string
				var qs = "";
				var qi = uri.indexOf("?");
				if (qi != -1) {
					qs = uri.substring(qi + 1);
					uri = uri.substring(0, qi);
				}

				var cmd = uri;
				requestCallback(socketId, cmd, qs, keepAlive);

			} else {
				console.error("invalid request: " + data);
				//socket.destroy(socketId);
				//this.openSockets.remove(socketId);
			}
		});
	};

	this.getUrlParam = function(queryStr, paramName) {
		var res = "";
		queryStr = "&" + queryStr;
		var i = queryStr.indexOf("&" + paramName + "=");
		if (i != -1) {
			var j = queryStr.indexOf("&", i + paramName.length + 2);
			if (j == -1)
				res = queryStr.substr(i + paramName.length + 2);
			else
				res = queryStr.substring(i + paramName.length + 2, j);
		}

		return res;
	};
	
	this.writeTextResponse = function(socketId, msg, keepAlive, contentType) {
		var ws = this;
		var file = {
			size : msg.length
		};
		var header = stringToUint8Array("HTTP/1.0 200 OK" 
				+ "\nContent-length: " + file.size 
				+ "\nContent-type: " + contentType
				+ "\nAccess-Control-Allow-Origin: *"
				+ (keepAlive ? "\nConnection: keep-alive" : "") + "\n\n");
		var outputBuffer = new ArrayBuffer(header.byteLength + file.size);
		var view = new Uint8Array(outputBuffer)
		view.set(header, 0);
		view.set(new stringToUint8Array(msg), header.byteLength);
		socket.write(socketId, outputBuffer, function(writeInfo) {
			console.log("WRITE", writeInfo);
			ws.writeResponse_End(socketId, keepAlive);
		});
		console.info("writeTextResponse:: end...");
	};

	this.writeResponse_Headers = function(socketId, fileSize, headers, keepAlive) {
		if (!headers) {
			headers = "Content-length: " + fileSize
				+ "\nContent-type: " + this.CONTENT_TYPE_TEXT_PLAIN
				+ "\nAccess-Control-Allow-Origin: *"
		}
		if (keepAlive) {
			headers += "\nConnection: keep-alive";
		} 
		var outputBuffer = stringToUint8Array("HTTP/1.0 200 OK\n" + headers + "\n\n", true);
		socket.write(socketId, outputBuffer, function(writeInfo) {
			console.log("WRITE_HEADERS socketId=" + socketId + " | bytesWritten=" + writeInfo.bytesWritten);
		});
	};

	this.writeResponse_Body_File = function(socketId, keepAlive, file) {
		
		var ws = this;
		var chunkSize = 10*1024*1024;
        var blobBuffer = file.slice(0, chunkSize, ws.CONTENT_TYPE_VIDEO_MP4); //buffer - blob type
		var chunkIndex = 0;
		var fileReader = new FileReader();
		
		fileReader.onload = function(e) {
			socket.write(socketId, e.target.result, function(writeInfo) {
				if (writeInfo.bytesWritten < 0) {
					console.log("WRITE_BODY socketId=" + socketId + " | error code=" + writeInfo.bytesWritten);
					ws.writeResponse_End(socketId, false);
				} else {
					console.log("WRITE_BODY socketId=" + socketId + " | bytesWritten=" + (chunkIndex * chunkSize + writeInfo.bytesWritten));
					chunkIndex++;
					blobBuffer = file.slice(chunkIndex * chunkSize, chunkIndex * chunkSize + chunkSize, ws.CONTENT_TYPE_VIDEO_MP4);
					if (blobBuffer.size > 0) {
						fileReader.readAsArrayBuffer(blobBuffer);
					} else {
						ws.writeResponse_End(socketId, keepAlive);
					}
				}
			});
		};
		
		fileReader.readAsArrayBuffer(blobBuffer);
	};

	this.writeResponse_End = function(socketId, keepAlive) {
		console.log("WRITE_END socketId=" + socketId + " | " + socketInfo.socketId + " | keepAlive=" + keepAlive)
		if (keepAlive) {
			readFromSocket(socketId);
		} else {
			socket.destroy(socketId);
			if (socketId == socketInfo.socketId) {
				console.error("socketId === socketInfo.socketId");
			}
			this.openSockets.remove(socketId);
		}
	};

	this.writeErrorResponse = function(socketId, errorCodeText, keepAlive) {
		var ws = this;
		var contentType = "text/plain"; // (file.type === "") ? "text/plain" : file.type;
		var header = stringToUint8Array("HTTP/1.0 " + errorCodeText
				+ "\nContent-length: 0"
				//+ "\nContent-type:" + contentType
				+ (keepAlive ? "\nConnection: keep-alive" : "") + "\n\n");
		var outputBuffer = new ArrayBuffer(header.byteLength);
		var view = new Uint8Array(outputBuffer)
		view.set(header, 0);
		socket.write(socketId, outputBuffer, function(writeInfo) {
			//console.log("WRITE_ERROR socketId=" + socketId + " | result=" + writeInfo.bytesWritten);
			ws.writeResponse_End(socketId, keepAlive);
		});
	};

	
	this.start = function() {
		console.log("webserver.start()");

		if (!requestCallback) {
			console.error("requestCallback is not defined");
			return;
		}

		socket.create("tcp", {}, function(_socketInfo) {
			console.log("socket created:", _socketInfo);
			socketInfo = _socketInfo;
			socket.listen(socketInfo.socketId, "127.0.0.1", port, 50, function(result) {
				console.log("LISTENING:", result);
				socket.accept(socketInfo.socketId, onAccept);
			});
		});
	};

	this.stop = function() {
		socket.destroy(socketInfo.socketId);
	};

	this.reset = function() {
		var ws=this;
		socket.getInfo(socketInfo.socketId, function(info) {
			if (!info.connected) {
				ws.stop();
				ws.start();
			}
		})
	};

	this.printSocketInfo = function() {
		socket.getInfo(socketInfo.socketId, function(info) {
			console.log("SocketInfo=",info);
		});
	};

};